//   Copyright 2012,2013 Vaughn Vernon
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package com.saasovation.agilepm.domain.model.product.backlogitem;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import com.saasovation.agilepm.domain.model.Entity;
import com.saasovation.agilepm.domain.model.team.TeamMember;
import com.saasovation.agilepm.domain.model.team.TeamMemberId;
import com.saasovation.agilepm.domain.model.tenant.TenantId;
import com.saasovation.common.domain.model.DomainEventPublisher;

/**
 * @实体：任务
 * @author learn
 */
public class Task extends Entity {

    /**
     * 待办事项ID
     */
    private BacklogItemId backlogItemId;
    /**
     * 描述
     */
    private String description;
    /**
     * 估算日志入口
     */
    private List<EstimationLogEntry> estimationLog;
    /**
     * 小时剩余数
     */
    private int hoursRemaining;
    /**
     * 名字
     */
    private String name;
    /**
     * 任务状态
     */
    private TaskStatus status;
    /**
     * 任务ID
     */
    private TaskId taskId;
    /**
     * 租户ID
     */
    private TenantId tenantId;
    /**
     * 团队成员编号
     */
    private TeamMemberId volunteer;

    /**
     * 获取估算日志入口清单
     * @return 返回估算日志入口清单
     */
    public List<EstimationLogEntry> allEstimationLogEntries() {
        return Collections.unmodifiableList(this.estimationLog());
    }

    /**
     * 获取内容描述
     * @return 获取内容描述
     */
    public String description() {
        return this.description;
    }

    /**
     * 获取名字
     * @return 返回名字
     */
    public String name() {
        return this.name;
    }

    /**
     * 获取任务状态
     * @return 返回任务状态
     */
    public TaskStatus status() {
        return this.status;
    }

    /**
     * 获取志愿者
     * @return 返回志愿者
     */
    public TeamMemberId volunteer() {
        return this.volunteer;
    }

    /**
     * 设置志愿者
     * @param aVolunteer 志愿者
     */
    protected void setVolunteer(TeamMemberId aVolunteer) {
        this.assertArgumentNotNull(aVolunteer, "The volunteer id must be provided.");
        this.assertArgumentEquals(this.tenantId(), aVolunteer.tenantId(), "The volunteer must be of the same tenant.");

        this.volunteer = aVolunteer;
    }

    @Override
    public boolean equals(Object anObject) {
        boolean equalObjects = false;

        if (anObject != null && this.getClass() == anObject.getClass()) {
            Task typedObject = (Task) anObject;
            equalObjects =
                    this.tenantId().equals(typedObject.tenantId()) &&
                            this.backlogItemId().equals(typedObject.backlogItemId()) &&
                            this.taskId().equals(typedObject.taskId());
        }

        return equalObjects;
    }

    @Override
    public int hashCode() {
        int hashCodeValue =
                + (73721 * 23)
                        + this.tenantId().hashCode()
                        + this.backlogItemId().hashCode()
                        + this.taskId().hashCode();

        return hashCodeValue;
    }

    /**
     * 由以下参数构造任务
     * @param aTenantId         租户ID
     * @param aBacklogItemId    待办事项ID
     * @param aTaskId           任务ID
     * @param aVolunteer        志愿者
     * @param aName             名字
     * @param aDescription      内容描述
     * @param aHoursRemaining   小时剩余数
     * @param aStatus           任务状态
     */
    protected Task(
            TenantId aTenantId,
            BacklogItemId aBacklogItemId,
            TaskId aTaskId,
            TeamMember aVolunteer,
            String aName,
            String aDescription,
            int aHoursRemaining,
            TaskStatus aStatus) {
        // 调用默认构造器
        this();

        this.setBacklogItemId(aBacklogItemId);
        this.setDescription(aDescription);
        this.setHoursRemaining(aHoursRemaining);
        this.setName(aName);
        this.setStatus(aStatus);
        this.setTaskId(aTaskId);
        this.setTenantId(aTenantId);
        this.setVolunteer(aVolunteer.teamMemberId());
    }
    /**
     * 默认构造器
     */
    private Task() {
        // 调用父类 默认构造器
        super();

        // 初始化估算日志
        this.setEstimationLog(new ArrayList<EstimationLogEntry>(0));
    }

    /**
     * 分配志愿者
     * @param aVolunteer    志愿者
     */
    protected void assignVolunteer(TeamMember aVolunteer) {
        // 首先，设置志愿者
        this.setVolunteer(aVolunteer.teamMemberId());

        // 然后，领域事件发布者发布：任务志愿者已分配
        DomainEventPublisher
                .instance()
                .publish(new TaskVolunteerAssigned(
                        this.tenantId(),
                        this.backlogItemId(),
                        this.taskId(),
                        this.volunteer().id()));
    }

    /**
     * 改变任务状态
     * @param aStatus   任务状态
     */
    protected void changeStatus(TaskStatus aStatus) {
        // 首先，设置新状态
        this.setStatus(aStatus);
        // 然后，领域事件发布者发布：任务状态已改变
        DomainEventPublisher
                .instance()
                .publish(new TaskStatusChanged(
                        this.tenantId(),
                        this.backlogItemId(),
                        this.taskId(),
                        this.status()));
    }

    /**
     * 描述为新内容
     * @param aDescription
     */
    protected void describeAs(String aDescription) {
        // 首先，设置新内容
        this.setDescription(aDescription);

        // 然后，领域事件发布者发布：任务已描述
        DomainEventPublisher
                .instance()
                .publish(new TaskDescribed(
                        this.tenantId(),
                        this.backlogItemId(),
                        this.taskId(),
                        this.description()));
    }

    /**
     * 估算小时剩余数
     * @param anHoursRemaining  小时剩余数
     */
    protected void estimateHoursRemaining(int anHoursRemaining) {
        if (anHoursRemaining < 0) {
            throw new IllegalArgumentException(
                    "Hours reminaing is illegal: " + anHoursRemaining);
        }
        // 如果新小时剩余数 不等于 旧值
        if (anHoursRemaining != this.hoursRemaining()) {
            // 首先，设置小时剩余数
            this.setHoursRemaining(anHoursRemaining);
            // 然后，领域事件发布者发布：任务小时剩余数已估算
            DomainEventPublisher
                    .instance()
                    .publish(new TaskHoursRemainingEstimated(
                            this.tenantId(),
                            this.backlogItemId(),
                            this.taskId(),
                            this.hoursRemaining()));
            // 如果新小时剩余数为零 并且 任务状态已完成；改变任务状态为已完成
            if (anHoursRemaining == 0 && !this.status().isDone()) {
                this.changeStatus(TaskStatus.DONE);
            } else if (anHoursRemaining > 0 && !this.status().isInProgress()) {
                // 否则，如果小时剩余数大于零，且任务状态不是进行中时，改变任务状态为进行中
                this.changeStatus(TaskStatus.IN_PROGRESS);
            }
            // 进行日志估算
            this.logEstimation(anHoursRemaining);
        }
    }

    /**
     * 重命名
     * @param aName 新名字
     */
    protected void rename(String aName) {
        // 首先，设置新名字
        this.setName(aName);

        // 领域事件发布者发布：任务已重命名
        DomainEventPublisher
                .instance()
                .publish(new TaskRenamed(
                        this.tenantId(),
                        this.backlogItemId(),
                        this.taskId(),
                        this.name()));
    }

    /**
     * 获取待办事项ID
     * @return  返回待办事项ID
     */
    protected BacklogItemId backlogItemId() {
        return this.backlogItemId;
    }

    /**
     * 设置待办事项ID
     * @param aBacklogItemId    待办事项ID
     */
    protected void setBacklogItemId(BacklogItemId aBacklogItemId) {
        if (aBacklogItemId == null) {
            throw new IllegalArgumentException("The backlogItemId is required.");
        }

        this.backlogItemId = aBacklogItemId;
    }

    /**
     * 设置内容描述
     * @param aDescription  内容描述
     */
    protected void setDescription(String aDescription) {
        if (aDescription == null || aDescription.length() == 0) {
            throw new IllegalArgumentException("Description is required.");
        }
        if (aDescription.length() > 65000) {
            throw new IllegalArgumentException("Description must be 65000 characters or less.");
        }

        this.description = aDescription;
    }

    /**
     * 获取估算日志清单
     * @return  估算日志清单
     */
    protected List<EstimationLogEntry> estimationLog() {
        return this.estimationLog;
    }

    /**
     * 设置估算日志清单
     * @param anEstimationLog   估算日志清单
     */
    protected void setEstimationLog(List<EstimationLogEntry> anEstimationLog) {
        this.estimationLog = anEstimationLog;
    }

    /**
     * 获取小时剩余数
     * @return  返回小时剩余数
     */
    protected int hoursRemaining() {
        return this.hoursRemaining;
    }

    /**
     * 设置小时剩余数
     * @param aHoursRemaining 小时剩余数
     */
    protected void setHoursRemaining(int aHoursRemaining) {
        this.hoursRemaining = aHoursRemaining;
    }

    /**
     * 设置名字
     * @param aName 名字
     */
    protected void setName(String aName) {
        if (aName == null || aName.length() == 0) {
            throw new IllegalArgumentException("Name is required.");
        }
        if (aName.length() > 100) {
            throw new IllegalArgumentException("Name must be 100 characters or less.");
        }

        this.name = aName;
    }

    /**
     * 设置任务状态
     * @param aStatus 任务状态
     */
    protected void setStatus(TaskStatus aStatus) {
        if (aStatus == null) {
            throw new IllegalArgumentException("Status is required.");
        }

        this.status = aStatus;
    }

    /**
     * 获取任务ID
     * @return  返回任务ID
     */
    protected TaskId taskId() {
        return this.taskId;
    }

    /**
     * 设置任务ID
     * @param aTaskId 任务ID
     */
    protected void setTaskId(TaskId aTaskId) {
        if (aTaskId == null) {
            throw new IllegalArgumentException("The taskId is required.");
        }
        this.taskId = aTaskId;
    }

    /**
     * 获取租户ID
     * @return 返回租户ID
     */
    protected TenantId tenantId() {
        return this.tenantId;
    }

    /**
     * 设置租户ID
     * @param aTenantId 租户ID
     */
    protected void setTenantId(TenantId aTenantId) {
        if (aTenantId == null) {
            throw new IllegalArgumentException("The tenantId is required.");
        }

        this.tenantId = aTenantId;
    }

    /**
     * 日志估算
     * @param anHoursRemaining 小时剩余数
     */
    private void logEstimation(int anHoursRemaining) {
        // 从估算日志入口获取当前日期
        Date today = EstimationLogEntry.currentLogDate();

        boolean updatedLogForToday = false;

        // 从估算日志获取迭代
        Iterator<EstimationLogEntry> iterator = this.estimationLog().iterator();

        // 如果没有更新成今天日期 并且 遍历估算日志入口
        while (!updatedLogForToday && iterator.hasNext()) {
            EstimationLogEntry entry = iterator.next();

            // 当日期匹配时，更新小时剩余数
            updatedLogForToday =
                    entry.updateHoursRemainingWhenDateMatches(
                            anHoursRemaining,
                            today);
        }

        // 如果没有更新成今天日期
        if (!updatedLogForToday) {
            // 追加日志入口
            this.estimationLog().add(
                    new EstimationLogEntry(
                            this.tenantId(),
                            this.taskId(),
                            today,
                            anHoursRemaining));
        }
    }
}
